package de.medieninf.android.sensimg;

import android.app.Service;
import android.content.Intent;
import android.content.res.Configuration;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

/**
 * Sensor handling as local service.  
 * Also manages possible host:port settings
 */
public class SensorService extends Service {
	private final static String TAG = "SensorService";
	
	private IBinder mBinder;
	private Object mLock;
	private SensorManager mSensorManager;
	private Sensor mAccelerometerSensor;
	private KeyboardState mKeyboardState;
	
	private boolean switchXY;

	private ArrayList<AccelerationListener> observers;
	private List<String> hostportsSend;
	private List<String> hostportsRetrieve;
	
	private static class HostPort {
		private String host;
		private int port;		
	}
	
	private HostPort currentHostPortSend;
	private int currentSendIndex;

	private HostPort currentHostPortRetrieve;
	private int currentRetrieveIndex;

	class MySensorEventListener implements SensorEventListener {
		List<AccelerationListener> als;
		@Override
		public void onSensorChanged(SensorEvent event) {
			if (!event.sensor.equals(mAccelerometerSensor)) {
				Log.w(TAG, "unknown sensor?");
				return;
			}
			float x = event.values[0];
			float y = event.values[1];
			float z = event.values[2];
			update(x,y,z);
		}
		public void update(float x, float y, float z) {
			synchronized (mLock) {
				als = new ArrayList<AccelerationListener>(observers);				
			}
			// Log.v(TAG, "got values " + x + " " + y + " " + z);
			if (switchXY) {
				float h = x;
				x = -y;
				y = h;
			} else  {
			}
			for (AccelerationListener al : als) {
				al.update(x, y, z);
			}
		}
		@Override
		public void onAccuracyChanged(Sensor sensor, int accuracy) {
			// Log.v(TAG, "onAccuracyChanged called");
		}		
	};
	private MySensorEventListener mSensorEventListener = new MySensorEventListener();
	
	public SensorService() {
		mLock = new Object();
		observers = new ArrayList<AccelerationListener>();
		switchXY = false;
		mKeyboardState = new KeyboardState();
	}
	
    public class LocalBinder extends Binder {
    	public LocalBinder() {
    		// startMonitoring(); // startup initially
    	}
        SensorService getService() {
            return SensorService.this;
        }
    }
	
	@Override
	public IBinder onBind(Intent intent) {
		synchronized (mLock) {
			if (mBinder == null) {
				mBinder = new LocalBinder();
			}
		}
		// Log.v(TAG, "onBind" + intent.getAction());
		return mBinder;
	}

	@Override
	public void onCreate() {
		super.onCreate();
		hostportsSend = new ArrayList<String>();
		for (String hp : getResources().getStringArray(R.array.send_addresses)) {
			hostportsSend.add(hp);
		}
		currentSendIndex = 0;
		currentHostPortSend = checkHostPort(hostportsSend.get(currentSendIndex));
		hostportsRetrieve = new ArrayList<String>();
		for (String hp : getResources().getStringArray(R.array.retrieve_addresses)) {
			hostportsRetrieve.add(hp);
		}
		currentRetrieveIndex = 0;
		currentHostPortRetrieve = checkHostPort(hostportsRetrieve.get(currentRetrieveIndex));
		// Log.v(TAG, "onCreate");
	};
	
	@Override
	public void onStart(Intent intent, int startId) {
		super.onStart(intent, startId);
		// Log.v(TAG, "onStart " + intent.getAction() + " " + startId);
	}
	
	@Override
	public void onRebind(Intent intent) {
		super.onRebind(intent);
		// Log.v(TAG, "onRebind" + intent.getAction());
	}
	
	@Override
	public void onDestroy() {
		stopMonitoring();
		super.onDestroy();
		// Log.v(TAG, "onDestroy");
	}
	
	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
			switchXY = true;
			// Log.v(TAG, "set orientation to landscape");
		} else {
			switchXY = false;
			// Log.v(TAG, "set orientation to portrait (or not landscape)");
		}
		super.onConfigurationChanged(newConfig);
	}
	
	public void register(AccelerationListener al) {
		synchronized (mLock) {
			if (!observers.contains(al)) {
				// Log.v(TAG, "registering AccelerationListerner " + al);
				observers.add(al);
			}
		}
	}
	
	public void unregister(AccelerationListener al) {
		synchronized (mLock) {
			if (observers.contains(al)) {
				// Log.v(TAG, "unregistering AccelerationListerner " + al);
				observers.remove(al);
			}
		}
	}
	
	public void unregisterAll() {
		synchronized (mLock) {
			observers.clear();
		}
	}
	
	public void startMonitoring() {    
		stopMonitoring();
		if (mSensorManager == null) {
			mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
			List<Sensor> l = mSensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
			if (l == null || l.isEmpty())
				throw new RuntimeException("Acceleration Sensor not available");
			mAccelerometerSensor = l.get(0);
			// Log.v(TAG, "got AccelerometerSensor");
		}
        mSensorManager.registerListener(
        		mSensorEventListener, 
        		mAccelerometerSensor,
        		SensorManager.SENSOR_DELAY_FASTEST);
		// Log.v(TAG, "started monitoring acceleration");
	}

	public void stopMonitoring(){
        try {
        	mSensorManager.unregisterListener(mSensorEventListener);
			// Log.v(TAG, "stopped monitoring acceleration");
        } catch(Exception e){
        	// ignore unregistering error
        }   
	}	
	
	public List<String> getHostPortsSend() {
		return hostportsSend;
	}

	public List<String> getHostPortsRetrieve() {
		return hostportsRetrieve;
	}
	
	public boolean setHostPortSend(String hp) {
		try {
			if (!hostportsSend.contains(hp)) {
				HostPort check = checkHostPort(hp);
				hostportsSend.add(hp);
				currentSendIndex = hostportsSend.indexOf(hp);
				currentHostPortSend = check;
			} else {
				currentSendIndex = hostportsSend.indexOf(hp);
				currentHostPortSend = checkHostPort(hp);				
			}
			return true;
		} catch (RuntimeException e) {
			return false;
		}
	}
	
	public boolean setHostPortRetrieve(String hp) {
		try {
			if (!hostportsRetrieve.contains(hp)) {
				HostPort check = checkHostPort(hp);
				hostportsRetrieve.add(hp);
				currentRetrieveIndex = hostportsRetrieve.indexOf(hp);
				currentHostPortRetrieve = check;
			} else {
				currentRetrieveIndex = hostportsRetrieve.indexOf(hp);
				currentHostPortRetrieve = checkHostPort(hp);				
			}
			return true;
		} catch (RuntimeException e) {
			return false;
		}
	}
	private HostPort checkHostPort(String hp) {
		int start=0;
		int next=0;
		int lastcolon = -1;
		
		while (next != -1) {
			next = hp.indexOf(':', start);
			if (next != -1) {
				lastcolon = next;
				start = lastcolon+1;
			}
		}
		if (lastcolon == -1) {
			throw new RuntimeException("host port does not contain ':'");			
		}
		HostPort ret = new HostPort();
		ret.host = hp.substring(0, lastcolon);
		try {
			ret.port = Integer.valueOf(hp.substring(lastcolon+1));
		} catch (NumberFormatException e) {
			throw new RuntimeException("port is not a number");
		}
		return ret;
	}
	
	public String getCurrentHostSend() {
		if (currentHostPortSend != null) {
			return currentHostPortSend.host;
		}
		return "";
	}
	
	public String getCurrentHostRetrieve() {
		if (currentHostPortRetrieve != null) {
			return currentHostPortRetrieve.host;
		}
		return "";
	}
	
	public int getCurrentPortSend() {
		if (currentHostPortSend != null) {
			return currentHostPortSend.port;
		}
		return -1;
	}
	
	public int getCurrentPortRetrieve() {
		if (currentHostPortRetrieve != null) {
			return currentHostPortRetrieve.port;
		}
		return -1;
	}
	
	public int getCurrrentIndexSend() {
		return currentSendIndex;
	}
	
	public int getCurrrentIndexRetrieve() {
		return currentRetrieveIndex;
	}
	
	public KeyboardState getKeyboardState() {
		return mKeyboardState;
	}
	
	private volatile Thread fakeDoIt;
	private float[] fakes = {
			0.0f, 0.0f, 0.0f,
			0.1f, 0.0f, 0.0f,
			0.2f, 0.0f, 0.0f,
			0.3f, 0.0f, 0.0f,
			0.4f, 0.0f, 0.0f,
			0.5f, 0.0f, 0.0f,
			0.6f, 0.0f, 0.0f,
			0.7f, 0.0f, 0.0f,
			0.8f, 0.0f, 0.0f,
			0.9f, 0.0f, 0.0f,
			1.0f, 0.0f, 0.0f,
			0.0f, 0.1f, 0.0f,
			0.0f, 0.2f, 0.0f,
			0.0f, 0.3f, 0.0f,
			0.0f, 0.4f, 0.0f,
			0.0f, 0.5f, 0.0f,
			0.0f, 0.6f, 0.0f,
			0.0f, 0.7f, 0.0f,
			0.0f, 0.8f, 0.0f,
			0.0f, 0.9f, 0.0f,
			0.0f, 1.0f, 0.0f,
			0.0f, 0.0f, 0.1f,		
			0.0f, 0.0f, 0.2f,		
			0.0f, 0.0f, 0.3f,		
			0.0f, 0.0f, 0.4f,		
			0.0f, 0.0f, 0.5f,		
			0.0f, 0.0f, 0.6f,		
			0.0f, 0.0f, 0.7f,		
			0.0f, 0.0f, 0.8f,		
			0.0f, 0.0f, 0.9f,		
			0.0f, 0.0f, 1.0f,
		};
	public void startFakeEvents() {
		fakeDoIt = new Thread() {
			volatile float lx, ly, lz;
			Runnable uiThreadRunner = new Runnable() {
				public void run() {
					mSensorEventListener.update(lx, ly, lz);
				}
			};
			public void run() {
				int i=0;
				while (fakeDoIt == Thread.currentThread()) {
					lx = fakes[i];
					ly = fakes[i+1];
					lz = fakes[i+2];
					i+=3;
					if (i >= fakes.length)
						i = 0;
					// runOnUiThread does not work in Service...???
					uiThreadRunner.run();
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						Log.w(TAG, "interrupted in fake values");
					}
				}
			}
		};
		fakeDoIt.start();
	}
	
	public void stopFakeEvents() {
		fakeDoIt = null;
	}
}
